import electron, { BrowserWindow, ipcMain, session, app, dialog, shell } from "electron"
import $ipcConstants, { IpcMessage, CompressTask } from "../../renderer/src/services/ipc/ipc.constant";
import axios, { AxiosProxyConfig } from "axios";
import fs from "fs-extra"
import path from "path"
import { defer, map, concatMap, catchError, of, Observable, tap, delay } from "rxjs";
import $util from "../util";

class CompressError extends Error {
  code: number = 500
  constructor(message: string, code: number) {
    super(message)
    this.code = code
  }
}

type ProxySourceType = {
  "anonymous": string,
  "check_count": number,
  "fail_count": number,
  "https": boolean,
  "last_status": boolean,
  "last_time": string,
  "proxy": string,
  "region": string,
  "source": string
}

class IpcService {
  windowMain: BrowserWindow | undefined = undefined
  cachedSet: {[prop: string]: CompressTask} = {}
  proxySource: ProxySourceType[] = []
  failedProxySource: string[] = []
  lastProxyUpdateTime: number  = 0
  updateProxyLocked: boolean = false
  bootstrap(win: BrowserWindow) {
    this.windowMain = win
    ipcMain.on($ipcConstants.ipcMessageId, (_event, data) => this.handleIpcMessage(data))
    console.log("1231313 bootstrap", $ipcConstants.ipcMessageId)
    this.updateProxySource()
  }

  updateProxySource(): Observable<undefined>  {
    return defer(() => of(null)).pipe(
      tap(() => this.updateProxyLocked = true),
      concatMap(() => {
        if ((Date.now() - this.lastProxyUpdateTime > 2 * 60 * 1000) || this.proxySource.length <= 0) {
          return axios.get("https://proxy-pool.wonew.top/all/")
            .then(res => {
              if (res.data && Array.isArray(res.data) && res.data.length > 0) {
                this.proxySource = res.data
                this.lastProxyUpdateTime = Date.now()
              }
              return undefined
            })
            .catch(() => undefined)
        }
        return of(undefined)
      }),
      tap(() => {
        this.updateProxyLocked = false
      })
    )
  }

  sendToIpcRender(payload: {methodName: string, data?: any, uuid: string}): void {
    this.windowMain?.webContents.send($ipcConstants.ipcMessageId, {
      methodName: payload.methodName,
      uuid: payload.uuid,
      data: payload?.data || {}
    })
  }

  handleIpcMessage(event: IpcMessage) {
    const { methodName, uuid } = event
    if (methodName === this.buildMethod($ipcConstants.exitApp)) {
      app.quit()
    }
    if (methodName === this.buildMethod($ipcConstants.openSaveDir)) {
      shell.openPath(event.data).then(res => {
        this.sendToIpcRender({
          methodName: this.buildMethod($ipcConstants.openSaveDir, "response"),
          uuid,
          data: res
        })
      })
    }
    if (methodName === this.buildMethod($ipcConstants.openFolderSelector)) {
      dialog.showOpenDialog({
        title: "请选择路径",
        properties: ['openDirectory'],
      }).then(result => {
        this.sendToIpcRender({
          methodName: this.buildMethod($ipcConstants.openFolderSelector, "response"),
          uuid,
          data: result
        })
      })
    }
    if (methodName === this.buildMethod($ipcConstants.getSystemDownloadDir)) {
      this.sendToIpcRender({
        methodName: this.buildMethod($ipcConstants.getSystemDownloadDir, "response"),
        uuid,
        data: app.getPath("downloads")
      })
    }
    if (methodName === this.buildMethod($ipcConstants.registerTask)) {
      const data = event.data as CompressTask[]
      data.forEach(task => {
        this.cachedSet[task.id] = task
      })
      this.sendToIpcRender({
        methodName: this.buildMethod($ipcConstants.registerTask, "response"),
        uuid, data: undefined
      })
      this.checkTasks()
      // this.requestToCompress(data[0]).subscribe()
    }
  }

  checkTasks() {
    let doingTasks = Object.values(this.cachedSet).filter(e => [1, 2, 3].includes(e.state))
    let readyTasks = Object.values(this.cachedSet).filter(e => [0].includes(e.state))
    if (doingTasks.length < 30) {
      let newTasks = readyTasks.slice(0, 30 - doingTasks.length)
      newTasks.forEach(task => this.startOneTask(task))
    }
  }

  startOneTask(task: CompressTask) {
    if (this.updateProxyLocked) return
    if (task.state !== 0) return
    of(null).pipe(
      concatMap(() => this.updateProxySource()),
      tap(() => {
        task.state = 1
        this.sendToIpcRender({
          methodName: this.buildMethod($ipcConstants.taskStateUpdate, "response"),
          uuid: $util.uuid(),
          data: {...task}
        })
      }),
      concatMap(() => this.requestStore(task)),
      tap(() => {
        task.state = 2
        this.sendToIpcRender({
          methodName: this.buildMethod($ipcConstants.taskStateUpdate, "response"),
          uuid: $util.uuid(),
          data: {...task}
        })
      }),
      concatMap(() => this.requestProcess(task)),
      tap(() => {
        task.state = 3
        this.sendToIpcRender({
          methodName: this.buildMethod($ipcConstants.taskStateUpdate, "response"),
          uuid: $util.uuid(),
          data: {...task}
        })
      }),
      concatMap(() => this.downloadResult(task)),
      map((data) => {
        const savePath = this.getTaskSavePath(task)
        fs.ensureFileSync(savePath)
        fs.writeFileSync(savePath, data)
        task.state = 4
        this.sendToIpcRender({
          methodName: this.buildMethod($ipcConstants.taskStateUpdate, "response"),
          uuid: $util.uuid(),
          data: {...task}
        })
      }),
      catchError(error => {
        console.log("error----", error.message, error.code)
        if (error instanceof CompressError && [601, 602, 603].includes(error.code)) {
          return of(null)
        }
        task.state = 5
        this.sendToIpcRender({
          methodName: this.buildMethod($ipcConstants.taskStateUpdate, "response"),
          uuid: $util.uuid(),
          data: {...task}
        })
        return of(null)
      })
    ).subscribe(
      () => this.checkTasks()
    )
  }

  requestProcess(task: CompressTask): Observable<undefined> {
    const proxy = this.getRandomProxy()
    return defer(() => 
      axios.post(
        'https://tinypng.com/backend/opt/process',
        {
          'key': task.storeResult?.key,
          'originalType': task.type,
          'originalSize': task.size,
          'convert': {
            'type': task.type
          }
        },
        {
          headers: {
            'Host': 'tinypng.com',
            'Cookie': '__stripe_mid=444f3831-6bdb-4acb-8613-dc3017d04872cf1e67; _ga=GA1.2.440019386.1702371243',
            'pragma': 'no-cache',
            'cache-control': 'no-cache',
            'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
            'sec-ch-ua-mobile': '?0',
            'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
            'sec-ch-ua-platform': '"macOS"',
            'origin': 'https://tinypng.com',
            'sec-fetch-site': 'same-origin',
            'sec-fetch-mode': 'cors',
            'sec-fetch-dest': 'empty',
            'referer': 'https://tinypng.com/',
            'accept-language': 'zh-CN,zh;q=0.9',
          },
          proxy: proxy?.format
        }
      )
    ).pipe(
      map(res => {
        if (!res?.data?.key) {
          throw new CompressError(`Request process failed.`, 702)
        }
        task.processResult = res.data
        return undefined
      }),
      catchError(error => {
        console.log(`requestProcess failed`, error.message, proxy?.source)
        throw error
      })
    )
  }

  requestStore(task: CompressTask): Observable<undefined> {
    const proxy = this.getRandomProxy()
    return defer(() => 
      axios.post(
        'https://tinypng.com/backend/opt/store',
        fs.readFileSync(task.path),
        {
          headers: {
            'Host': 'tinypng.com',
            'Cookie': '__stripe_mid=444f3831-6bdb-4acb-8613-dc3017d04872cf1e67; _ga=GA1.2.440019386.1702371243',
            'pragma': 'no-cache',
            'cache-control': 'no-cache',
            'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
            'accept': 'application/json, text/plain, */*',
            'content-type': 'application/x-www-form-urlencoded',
            'sec-ch-ua-mobile': '?0',
            'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
            'sec-ch-ua-platform': '"macOS"',
            'origin': 'https://tinypng.com',
            'sec-fetch-site': 'same-origin',
            'sec-fetch-mode': 'cors',
            'sec-fetch-dest': 'empty',
            'referer': 'https://tinypng.com/',
            'accept-language': 'zh-CN,zh;q=0.9'
          },
          proxy: proxy?.format
        }
      )
    ).pipe(
      map(res => {
        if (!res?.data?.key) {
          throw new CompressError(`Request store failed.`, 701)
        }
        task.storeResult = res.data
        return undefined
      }),
      catchError(error => {
        console.log(`requestStore failed`, error.message, proxy?.source)
        throw error
      })
    )
  }

  requestToCompress(task: CompressTask): Observable<CompressTask> {
    const proxyConfig = this.getRandomProxy()
    console.log("proxyConfig-----", proxyConfig)
    return defer(() =>
      axios.post(
        'https://tinify.cn/backend/opt/shrink',
        fs.readFileSync(task.path),
        {
          headers: {
            'Host': 'tinify.cn',
            'Cookie': '__stripe_mid=1c0300b6-e995-4c6e-ad6b-16aacc2ba0999abb07; _ga=GA1.2.733535809.1701938564; _gid=GA1.2.922822026.1702364452; __stripe_sid=54b1db34-5e37-40d7-bd74-934453c7d3f2dde376; _gat=1',
            'pragma': 'no-cache',
            'cache-control': 'no-cache',
            'sec-ch-ua': '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"',
            'sec-ch-ua-platform': '"macOS"',
            'sec-ch-ua-mobile': '?0',
            'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
            'content-type': 'image/png',
            'accept': '*/*',
            'origin': 'https://tinify.cn',
            'sec-fetch-site': 'same-origin',
            'sec-fetch-mode': 'cors',
            'sec-fetch-dest': 'empty',
            'referer': 'https://tinify.cn/',
            'accept-language': 'zh-CN,zh;q=0.9'
          },
          proxy: proxyConfig?.format,
          onUploadProgress: (progressEvent) => {
            if (progressEvent.total) {
              const progress = progressEvent.loaded / progressEvent.total
              task.progress.upload = progress
              if (progress === 1) {
                task.state = 2
              }
              this.sendToIpcRender({
                methodName: this.buildMethod($ipcConstants.taskStateUpdate, "response"),
                uuid: $util.uuid(),
                data: {...task}
              })
            }
          },
        }
      )
    ).pipe(
      map(res => {
        if (!res.data || !res.data?.output?.url) {
          throw new CompressError(`RequestToCompress failed`, 501)
        }
        task.uploadResult = res.data
        return task
      }),
      catchError(error => {
        if (proxyConfig?.source) {
          this.failedProxySource.push(proxyConfig?.source.proxy)
        }
        console.log(`error in requestToCompress: ${error.message}, proxy: ${JSON.stringify(proxyConfig?.source)}`)
        throw error
      })
    )
  }
  downloadResult(task: CompressTask): Observable<Buffer> {
    const compressedFileUrl = this.getCompressedFileUrl(task)
    return defer(() => 
      axios.get(compressedFileUrl, {
        responseType: "arraybuffer",
        onDownloadProgress: (progressEvent) => {
          if (progressEvent.total) {
            const progress = progressEvent.loaded / progressEvent.total
            task.progress.download = progress
            this.sendToIpcRender({
              methodName: this.buildMethod($ipcConstants.taskStateUpdate, "response"),
              uuid: $util.uuid(),
              data: {...task}
            })
          }
        },
      })
    ).pipe(
      map(res => res.data),
      catchError(error => {
        console.log(`error in downloadResult: ${error.message}`)
        throw error
      })
    )
  }

  getCompressedFileUrl(task: CompressTask) {
    return `${task.processResult?.url}/${task.name}`
    return `${task.uploadResult?.output.url}/${task.name}`
  }

  getTaskSavePath(task: CompressTask) {
    if (task.saveOptions.replaceOrigin) {
      return task.path
    }
    return path.join(task.saveOptions.saveDir || task.saveOptions.systemDownloadDir, task.name)
  }

  getRandomProxy(): {format: AxiosProxyConfig, source: ProxySourceType} | undefined {
    console.log(`failedProxySource`, this.failedProxySource)
    let validSource = this.proxySource.filter(e => !this.failedProxySource.includes(e.proxy))
    const idx = Math.floor(validSource.length * Math.random())
    let source = validSource[idx]
    if (!source) return undefined
    return {
      format: {
        host: source.proxy.split(":")[0],
        port: +source.proxy.split(":")[1],
        protocol: "http"
      },
      source: source
    }
  }

  buildMethod(methodName: string, suffix?: "request" | "response"): string {
    return `${methodName}.${suffix || "request"}`
  }

  debug() {
    // const file = fs.readFileSync("/Users/ozeus/Downloads/test666.png")
    // .then(e => {
    //     console.log("31313", e.data)
    //   });
  }
}

const $ipcMain = new IpcService()
export default $ipcMain


